﻿
namespace Patterns4Net.PatternEnforcer.Core.Patterns
{
    using System;
    using System.Collections.Generic;
    using Mono.Cecil;
    using Patterns4Net.Attributes;
    using Patterns4Net.PatternEnforcer.Core.ClassCheckers;

    public class FluentPatternChecker<TPattern> : PatternCheckerBase<TPattern>
        where TPattern : class, IPattern
    {
        private readonly IList<MemberChecker<TypeDefinition>> typeCheckers = new List<MemberChecker<TypeDefinition>>();

        private readonly IList<MemberChecker<MethodDefinition>> methodCheckers = new List<MemberChecker<MethodDefinition>>();

        /// <summary>
        /// We use special array for checkers of this pattern (TPattern) and for checkers for other patterns 
        /// that may be acquired by transformation of TPattern instance.
        /// This is because we want this checkers to be specialized to TPattern and if we used one array for all, 
        /// then we would have to use <see cref="IPattern"/> as generic type.
        /// </summary>
        private readonly IList<IChecker<TPattern>> patternCheckers = new List<IChecker<TPattern>>();

        /// <summary>
        /// Other pattern checkers are checkers for other patterns that may be acquired by transformation of TPattern instances to 
        /// other patterns.
        /// </summary>
        private readonly IList<PatternSelectorAndChecker> otherPatternCheckers = new List<PatternSelectorAndChecker>();

        private readonly IList<ConditionalChecker> conditionalCheckers = new List<ConditionalChecker>();

        /// <summary>
        /// Initializes a new instance of the <see cref="FluentPatternChecker{TPattern}"/> class.
        /// The class cannot be declared as abstract because we want to instantiate it in <see cref="If"/> method.
        /// </summary>
        protected FluentPatternChecker()
        {            
        }

        public FluentPatternChecker<TPattern> If(Func<TPattern, bool> predicate)
        {
            var checker = new FluentPatternChecker<TPattern>();
            var conditionalChecker = new ConditionalChecker(predicate, checker);
            this.conditionalCheckers.Add(conditionalChecker);
            return checker;
        }

        public TypeSelector Type(Func<TPattern, TypeDefinition> target)
        {
            return new TypeSelector(this, target);
        }

        public MethodSelector Method(Func<TPattern, MethodDefinition> target)
        {
            return new MethodSelector(this, target);
        }

        public FluentPatternChecker<TPattern> Check(Func<TPattern, bool> check, Func<TPattern, string> error)
        {
            this.patternCheckers.Add(new LambdaChecker<TPattern>(check, error));
            return this;
        }

        public FluentPatternChecker<TPattern> Checker<TPatternChecker>(Func<TPattern, IPattern> patternSelector) 
            where TPatternChecker : IChecker<IPattern>, new()
        {
            this.otherPatternCheckers.Add(new PatternSelectorAndChecker(patternSelector, new TPatternChecker()));
            return this;
        }

        public FluentPatternChecker<TPattern> Checker(Func<TPattern, IPattern> patternSelector, IChecker<IPattern> checker)
        {
            this.otherPatternCheckers.Add(new PatternSelectorAndChecker(patternSelector, checker));
            return this;
        }

        protected sealed override CheckerResult Check(TPattern pattern)
        {
            var result = new CheckerResultBuilder();

            foreach (var patternChecker in this.patternCheckers)
            {
                result.AddChildResult(patternChecker.Check(pattern));
            }

            foreach (var patternSelectorAndChecker in this.otherPatternCheckers)
            {
                var checker = patternSelectorAndChecker.Checker;
                var otherPattern = patternSelectorAndChecker.PatternSelector.Invoke(pattern);
                result.AddChildResult(checker.Check(otherPattern));
            }

            foreach (var typeChecker in this.typeCheckers)
            {
                var typeToBeChecked = typeChecker.GetTypeToBeChecked(pattern);
                result.AddChildResult(typeChecker.CreateChecker(pattern).Check(typeToBeChecked));
            }

            foreach (var methodChecker in this.methodCheckers)
            {
                var methodToBeChecked = methodChecker.GetTypeToBeChecked(pattern);
                result.AddChildResult(methodChecker.CreateChecker(pattern).Check(methodToBeChecked));
            }

            foreach (var conditionalChecker in this.conditionalCheckers)
            {
                if (conditionalChecker.IsConditionTrue(pattern))
                {
                    result.AddChildResult(conditionalChecker.Checker.Check(pattern));
                }
            }

            this.Check(pattern, result);
            return result.Build();
        }

        /// <summary>
        /// This method could be used to add some custom complex checking.
        /// </summary>
        /// <param name="pattern">Pattern to be checked.</param>
        /// <param name="result">The result builder. It may already contain some errors.</param>
        protected virtual void Check(TPattern pattern, CheckerResultBuilder result)
        {
        }

        #region Nested Classes

        public abstract class Selector<TMember, TThis> where TMember : class, IMemberDefinition where TThis : class
        {
            protected Selector(FluentPatternChecker<TPattern> parent, Func<TPattern, TMember> getTypeToBeChecked)
            {
                this.Parent = parent;
                this.GetTypeToBeChecked = getTypeToBeChecked;
            }

            protected FluentPatternChecker<TPattern> Parent { get; private set; }

            protected Func<TPattern, TMember> GetTypeToBeChecked { get; private set; }

            public TThis Check(Func<TMember, bool> check, Func<TMember, string> error)
            {
                return this.Checker(x => new LambdaChecker<TMember>(check, error));
            }

            public TThis Check(Func<TPattern, TMember, bool> check, Func<TPattern, TMember, string> error)
            {
                return this.Checker(pattern => 
                    new LambdaChecker<TMember>(
                        member => check(pattern, member), 
                        member => error(pattern, member)));
            }

            public TThis Check(Func<TMember, bool> check, Func<TPattern, TMember, string> error)
            {
                return this.Checker(pattern =>
                    new LambdaChecker<TMember>(
                        check,
                        member => error(pattern, member)));
            }

            public TThis Check(Func<TPattern, TMember, bool> check, Func<TMember, string> error)
            {
                return this.Checker(pattern =>
                    new LambdaChecker<TMember>(
                        member => check(pattern, member),
                        error));
            }

            public TThis Checker<TChecker>() where TChecker : IChecker<TMember>, new()
            {
                var checker = new TChecker();
                return this.Checker(x => checker);
            }

            public TThis Checker(IChecker<TMember> checker)
            {
                return this.Checker(pattern => checker);
            }

            public abstract TThis Checker(Func<TPattern, IChecker<TMember>> createChecker);
        }

        public class MethodSelector : Selector<MethodDefinition, MethodSelector>
        {
            public MethodSelector(FluentPatternChecker<TPattern> parent, Func<TPattern, MethodDefinition> getTypeToBeChecked)
                : base(parent, getTypeToBeChecked)
            {
            }

            public override MethodSelector Checker(Func<TPattern, IChecker<MethodDefinition>> createChecker)
            {
                var memberChecker = new MemberChecker<MethodDefinition>(this.GetTypeToBeChecked, createChecker);
                this.Parent.methodCheckers.Add(memberChecker);
                return this;
            }
        }

        public class TypeSelector : Selector<TypeDefinition, TypeSelector>
        {
            public TypeSelector(FluentPatternChecker<TPattern> parent, Func<TPattern, TypeDefinition> getTypeToBeChecked)
                : base(parent, getTypeToBeChecked)
            {
            }

            public TypeMethodsSelector Methods(Func<TPattern, MethodDefinition, bool> selector)
            {
                return new TypeMethodsSelector(this.Parent, this.GetTypeToBeChecked, selector);
            }

            public TypeMethodsSelector Methods(Func<MethodDefinition, bool> selector)
            {
                return new TypeMethodsSelector(this.Parent, this.GetTypeToBeChecked, (pattern, method) => selector.Invoke(method));
            }

            public TypeMethodsSelector Methods()
            {
                return new TypeMethodsSelector(this.Parent, this.GetTypeToBeChecked, (p, m) => true);
            }

            public override TypeSelector Checker(Func<TPattern, IChecker<TypeDefinition>> createChecker)
            {
                var memberChecker = new MemberChecker<TypeDefinition>(this.GetTypeToBeChecked, createChecker);
                this.Parent.typeCheckers.Add(memberChecker);
                return this;
            }
        }

        public class TypeMethodsSelector
        {
            private readonly FluentPatternChecker<TPattern> parent;

            private readonly Func<TPattern, TypeDefinition> getTypeToBeChecked;

            private readonly Func<TPattern, MethodDefinition, bool> selector;

            public TypeMethodsSelector(
                FluentPatternChecker<TPattern> parent, 
                Func<TPattern, TypeDefinition> getTypeToBeChecked, 
                Func<TPattern, MethodDefinition, bool> selector)
            {
                this.parent = parent;
                this.getTypeToBeChecked = getTypeToBeChecked;
                this.selector = selector;
            }

            public TypeMethodsSelector Check(Func<MethodDefinition, bool> check, Func<MethodDefinition, string> error)
            {
                return this.Checker(new LambdaChecker<MethodDefinition>(check, error));
            }

            public TypeMethodsSelector Check(Func<TPattern, MethodDefinition, bool> check, Func<TPattern, MethodDefinition, string> error)
            {
                this.parent.typeCheckers.Add(
                    new MemberChecker<TypeDefinition>(
                        this.getTypeToBeChecked,
                        pattern => new TypeMethodsChecker(
                            method => this.selector.Invoke(pattern, method), 
                            new LambdaChecker<MethodDefinition>(
                                method => check(pattern, method), 
                                method => error(pattern, method)))));
                return this;                
            }

            public TypeMethodsSelector Check(Func<MethodDefinition, bool> check, Func<TPattern, MethodDefinition, string> error)
            {
                this.parent.typeCheckers.Add(
                    new MemberChecker<TypeDefinition>(
                        this.getTypeToBeChecked,
                        pattern => new TypeMethodsChecker(
                            method => this.selector.Invoke(pattern, method),
                            new LambdaChecker<MethodDefinition>(
                                check,
                                method => error(pattern, method)))));
                return this;
            }

            public TypeMethodsSelector Check(Func<TPattern, MethodDefinition, bool> check, Func<MethodDefinition, string> error)
            {
                this.parent.typeCheckers.Add(
                    new MemberChecker<TypeDefinition>(
                        this.getTypeToBeChecked,
                        pattern => new TypeMethodsChecker(
                            method => this.selector.Invoke(pattern, method),
                            new LambdaChecker<MethodDefinition>(
                                method => check(pattern, method),
                                error))));
                return this;
            }

            public TypeMethodsSelector Checker<TChecker>() where TChecker : IChecker<MethodDefinition>, new()
            {
                var checker = new TChecker();
                return this.Checker(checker);
            }

            public TypeMethodsSelector Checker(IChecker<MethodDefinition> checker)
            {
                this.parent.typeCheckers.Add(
                    new MemberChecker<TypeDefinition>(
                        this.getTypeToBeChecked, 
                        pattern => new TypeMethodsChecker(method => this.selector.Invoke(pattern, method), checker)));
                return this;
            }
        }

        public class MemberChecker<TMember> where TMember : class, IMemberDefinition
        {
            private readonly Func<TPattern, TMember> getTypeToBeChecked;

            private readonly Func<TPattern, IChecker<TMember>> createChecker;

            public MemberChecker(Func<TPattern, TMember> getTypeToBeChecked, Func<TPattern, IChecker<TMember>> createChecker)
            {
                this.getTypeToBeChecked = getTypeToBeChecked;
                this.createChecker = createChecker;
            }

            public MemberChecker(Func<TPattern, TMember> getTypeToBeChecked, IChecker<TMember> checker)
            {
                this.getTypeToBeChecked = getTypeToBeChecked;
                this.createChecker = x => checker;
            }

            public IChecker<TMember> CreateChecker(TPattern pattern)
            {
                return this.createChecker.Invoke(pattern);
            }

            public TMember GetTypeToBeChecked(TPattern pattern)
            {
                return this.getTypeToBeChecked(pattern);
            }
        }

        public class ConditionalChecker
        {
            private readonly Func<TPattern, bool> predicate;

            public ConditionalChecker(Func<TPattern, bool> predicate, FluentPatternChecker<TPattern> checker)
            {
                this.predicate = predicate;
                this.Checker = checker;
            }

            public FluentPatternChecker<TPattern> Checker { get; private set; }

            public bool IsConditionTrue(TPattern pattern)
            {
                return this.predicate.Invoke(pattern);
            }
        }

        public class PatternSelectorAndChecker
        {
            public PatternSelectorAndChecker(Func<TPattern, IPattern> patternSelector, IChecker<IPattern> checker)
            {
                this.PatternSelector = patternSelector;
                this.Checker = checker;
            }

            public Func<TPattern, IPattern> PatternSelector { get; private set; }

            public IChecker<IPattern> Checker { get; private set; }
        }

        #endregion
    }
}
